using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using Transaction = javax.transaction.Transaction;
	using TransactionManager = javax.transaction.TransactionManager;

	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using NotInTransactionException = org.neo4j.graphdb.NotInTransactionException;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using IdGenerator = org.neo4j.kernel.impl.persistence.IdGenerator;
	using PersistenceManager = org.neo4j.kernel.impl.persistence.PersistenceManager;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	public class PropertyIndexManager
	{
		private ArrayMap<string, List<PropertyIndex>> indexMap = new ArrayMap<string, List<PropertyIndex>>(5, true, false);
		private ArrayMap<int, PropertyIndex> idToIndexMap = new ArrayMap<int, PropertyIndex>(9, true, false);

		private ArrayMap<Transaction, TxCommitHook> txCommitHooks = new ArrayMap<Transaction, TxCommitHook>(5, true, false);

		private readonly TransactionManager transactionManager;
		private readonly PersistenceManager persistenceManager;
		private readonly IdGenerator idGenerator;

		private bool hasAll = false;

		internal PropertyIndexManager(TransactionManager transactionManager, PersistenceManager persistenceManager, IdGenerator idGenerator)
		{
			this.transactionManager = transactionManager;
			this.persistenceManager = persistenceManager;
			this.idGenerator = idGenerator;
		}

		internal virtual void clear()
		{
			indexMap = new ArrayMap<string, List<PropertyIndex>>(5, true, false);
			idToIndexMap = new ArrayMap<int, PropertyIndex>(9, true, false);
			txCommitHooks.Clear();
		}

		public virtual IEnumerable <PropertyIndex> index(string key)
		{
			List<PropertyIndex> list = indexMap.get(key);
			TxCommitHook hook = txCommitHooks.get(getTransaction());
			if (hook != null)
			{
				PropertyIndex index = hook.getIndex(key);
				if (index != null)
				{
					List<PropertyIndex> added = new List<PropertyIndex>();
					if (list != null)
					{
						added.AddRange(list);
					}
					added.Add(index);
					return added;
				}
			}
			if (list == null)
			{
				list = Collections.emptyList();
			}
			return list;
		}

		internal virtual void setHasAll(bool status)
		{
			hasAll = status;
		}

		internal virtual bool hasAll()
		{
			return hasAll;
		}

		public virtual bool hasIndexFor(int keyId)
		{
			return idToIndexMap.get(keyId) != null;
		}

		internal virtual void addPropertyIndexes(PropertyIndexData[] indexes)
		{
			foreach (PropertyIndexData rawIndex in indexes)
			{
				addPropertyIndex(new PropertyIndex(rawIndex.Value, rawIndex.getKeyId()));
			}
		}

		internal virtual void addPropertyIndex(PropertyIndexData rawIndex)
		{
			addPropertyIndex(new PropertyIndex(rawIndex.Value, rawIndex.getKeyId()));
		}

		public virtual PropertyIndex getIndexFor(int keyId)
		{
			PropertyIndex index = idToIndexMap.get(keyId);
			if (index == null)
			{
				TxCommitHook commitHook = txCommitHooks.get(getTransaction());
				if (commitHook != null)
				{
					index = commitHook.getIndex(keyId);
					if (index != null)
					{
						return index;
					}
				}
				string indexString;
				indexString = persistenceManager.loadIndex(keyId);
				if (indexString == null)
				{
					throw new NotFoundException("Index not found [" + keyId + "]");
				}
				index = new PropertyIndex(indexString, keyId);
				addPropertyIndex(index);
			}
			return index;
		}

	// need synch here so we don't create multiple lists
		[MethodImpl(MethodImplOptions.Synchronized)]
		private void addPropertyIndex(PropertyIndex index)
		{
			List<PropertyIndex> list = indexMap.get(index.Key);
			if (list == null)
			{
				list = new CopyOnWriteArrayList<PropertyIndex>();
				indexMap.put(index.Key, list);
			}
			list.Add(index);
			idToIndexMap.put(index.getKeyId(), index);
		}

		private Transaction getTransaction()
		{
			try
			{
				return transactionManager.getTransaction();
			}
			catch (Exception e)
			{
				throw new TransactionFailureException("Unable to get transaction.", e);
			}
		}

	// concurent transactions may create duplicate keys, oh well
		internal virtual PropertyIndex createPropertyIndex(string key)
		{
			Transaction tx = getTransaction();
			if (tx == null)
			{
				throw new NotInTransactionException("Unable to create property index for " + key);
			}
			TxCommitHook hook = txCommitHooks.get(tx);
			if (hook == null)
			{
				hook = new TxCommitHook();
				txCommitHooks.put(tx, hook);
			}
			PropertyIndex index = hook.getIndex(key);
			if (index != null)
			{
				return index;
			}
			int id = idGenerator.nextId(typeof(PropertyIndex));
			index = new PropertyIndex(key, id);
			hook.addIndex(index);
			persistenceManager.createPropertyIndex(key, id);
			return index;
		}

		internal virtual void setRollbackOnly()
		{
			try
			{
				transactionManager.setRollbackOnly();
			}
			catch (javax.transaction.SystemException se)
			{
				se.printStackTrace();
			}
		}

		internal virtual void commit(Transaction tx)
		{
			if (tx != null)
			{
				TxCommitHook hook = txCommitHooks.Remove(tx);
				if (hook != null)
				{
					foreach (PropertyIndex index in hook.getAddedPropertyIndexes())
					{
						addPropertyIndex(index);
					}
				}
			}
		}

		internal virtual void rollback(Transaction tx)
		{
			txCommitHooks.Remove(tx);
		}

		private class TxCommitHook
		{
			private Map<string, PropertyIndex> createdIndexes = new Dictionary<string, PropertyIndex>();
			private Map<int, PropertyIndex> idToIndex = new Dictionary<int, PropertyIndex>();


			internal TxCommitHook()
			{
			}

			internal virtual void addIndex(PropertyIndex index)
			{
				Debug.Assert(!createdIndexes.containsKey(index.Key));
				createdIndexes.put(index.Key, index);
				idToIndex.put(index.getKeyId(), index);
			}

			internal virtual PropertyIndex getIndex(string key)
			{
				return createdIndexes.get(key);
			}

			internal virtual PropertyIndex getIndex(int keyId)
			{
				return idToIndex.get(keyId);
			}

			internal virtual IEnumerable <PropertyIndex> getAddedPropertyIndexes()
			{
				return createdIndexes.values();
			}
		}
	}
}